﻿using System;
using Microsoft.Xna.Framework;
using ShevaEngine.Core.Foundation.Emitters;
using ShevaEngine.Core.Interfaces;
using ShevaEngine.Core.Modules.Graphics;
using ShevaEngine.Core.Modules.Graphics.Cameras;
using ShevaEngine.Core.Modules.Materials;
using ShevaEngine.Core.Modules.Scenes;

namespace ShevaEngine.Core.Modules.ParticleSystems
{
    /// <summary>
    /// Particle system.
    /// </summary>
    public class ParticleSystem : SceneObject, IUpdateAble
    {
        /// <summary>World matrices.</summary>
        internal Matrix[] WorldMatrices;
        /// <summary>DrawCallKey.</summary>
        internal DrawCallKey DrawCallKey;
        /// <summary>Draw call.</summary>
        internal DrawCall DrawCall;
        /// <summary>Emitter.</summary>
        internal IEmitter Emitter;
        /// <summary>Time parameter.</summary>
        internal GraphicsMaterialParameterFloat TimeParam;
        /// <summary>Time parameter.</summary>
        internal GraphicsMaterialParameterFloat DurationParam;
        /// <summary>Start size.</summary>
        internal GraphicsMaterialParameterVector2 StartSize;
        /// <summary>End size.</summary>
        internal GraphicsMaterialParameterVector2 EndSize;
        /// <summary>Start velocity.</summary>
        internal GraphicsMaterialParameterVector3 StartVelocity;
        /// <summary>End velocity.</summary>
        internal GraphicsMaterialParameterVector3 EndVelocity;
        /// <summary>Gravity.</summary>
        internal GraphicsMaterialParameterVector3 Gravity;
        /// <summary>Particles count to ipdate.</summary>
        internal int ParticlesCount;
        /// <summary>Particles count to ipdate.</summary>
        private int ParticlesCountToUpdate;
        /// <summary>Total update time.</summary>
        private double TotalUpdateTime;        
        /// <summary>Last updated.</summary>
        private int LastUpdated;
        /// <summary>Random.</summary>
        private Random Random;

        /// <summary>
        /// Constructor.
        /// </summary>
        internal ParticleSystem()
        {
            this.WorldMatrices = null;
            this.LastUpdated = 0;
            this.TotalUpdateTime = 0;
            this.Random = new Random();
        }        

        /// <summary>
        /// Method setup matrix.
        /// </summary>
        /// <param name="matrix"></param>
        internal Matrix SetupMatrix(double timeSpan)
        {
            Vector3 newPosition = this.Emitter.GetRandomPosition();

            newPosition += (float)(Math.Min(0.05, timeSpan) * this.Random.NextDouble()) * this.StartVelocity.Value;

            Matrix output = Matrix.CreateScale(10f) * Matrix.CreateTranslation(this.World.Translation + newPosition);

            output.M13 = this.TimeParam.Value;
            output.M23 = (float)this.Random.NextDouble();

            return output;
        }

        /// <summary>
        /// Override method AddInRenderingPipeline.
        /// </summary>
        /// <param name="renderingPipeline"></param>
        /// <param name="camera"></param>
        public override void AddInRenderingPipeline(RenderingPipeline renderingPipeline, Camera camera)
        {
            renderingPipeline.AddOpaqueGraphicsObjects(this.DrawCallKey, this.DrawCall, this.WorldMatrices);
        }

        /// <summary>
        /// Override method AddInRenderingPipeline.
        /// </summary>
        /// <param name="renderingPipeline"></param>
        /// <param name="camera"></param>
        public override void AddInRenderingPipeline(RenderingPipeline renderingPipeline, Vector3 position, float distance)
        {
            renderingPipeline.AddOpaqueGraphicsObjects(this.DrawCallKey, this.DrawCall, this.WorldMatrices);
        }

        /// <summary>
        /// Method Update().
        /// </summary>
        /// <param name="time"></param>
        public void Update(GameTime time)
        {
            this.TimeParam.Value += (float)time.ElapsedGameTime.TotalSeconds;

            this.TotalUpdateTime += time.ElapsedGameTime.TotalSeconds;

            this.ParticlesCountToUpdate = (int)((this.TotalUpdateTime / this.DurationParam.Value) * this.ParticlesCount);

            if (this.ParticlesCountToUpdate > 0)
            {
                this.TotalUpdateTime -= this.ParticlesCountToUpdate * (this.DurationParam.Value / this.ParticlesCount);                

                for (int i = this.ParticlesCountToUpdate; i >= 0; i--)
                    this.WorldMatrices[(i + this.LastUpdated) % this.ParticlesCount] = this.SetupMatrix(time.ElapsedGameTime.TotalSeconds);

                this.LastUpdated = (this.LastUpdated + this.ParticlesCountToUpdate) % this.ParticlesCount;
            }
        }
    }
}
